#ifndef MTL_CONDITION_VARIABLE_HPP
#define MTL_CONDITION_VARIABLE_HPP

extern "C" {
#include <pthread.h>
}
#include "exception.hpp"

namespace mtl {
  class condition_variable {
  public:
    condition_variable() {
      pthread_cond_init(&m_c, NULL);
    }
    ~condition_variable() {
      pthread_cond_destroy(&m_c);
    }

    void wait(mutex& m) {
      pthread_cond_wait(&m_c, &m.pmt());
    }

    template<class _Pred> void wait(mutex& m, _Pred pred) {
      while (!pred()) {
        wait(m);
      }
    }

    template<class _Lock> void wait(_Lock& l) {
      wait(l.mutex());
    }

    void notify_one() {
      pthread_cond_signal(&m_c);
    }

    void notify_all() {
      pthread_cond_broadcast(&m_c);
    }

  private:
    pthread_cond_t  m_c;
  };

  class barrier {
  private:
    pthread_barrier_t m_b;
  private:
    barrier(const barrier&);
    barrier& operator=(const barrier&);
  public:
    explicit barrier(const unsigned int count) {
      pthread_barrier_init(&m_b, NULL, count);
    }

    ~barrier() {
      pthread_barrier_destroy(&m_b);
    }

    void wait() {
      const int err = pthread_barrier_wait(&m_b);
      if (err != 0 && err != PTHREAD_BARRIER_SERIAL_THREAD) {
        throw system_error(
            error_code(err, system_category()),
            "failed to wait barrier");
      }
    }
  };

}
#endif
